package 面向对象;

一个class可以包含多个field，例如，我们给Person类就定义了两个field：

class Person {
    public String name;
    public int age;
}
但是，直接把field用public暴露给外部可能会破坏封装性。比如，代码可以这样写：

Person ming = new Person();
ming.name = "Xiao Ming";
ming.age = -99; // age设置为负数 
显然，直接操作field，容易造成逻辑混乱。为了避免外部代码直接去访问field，我们可以用private修饰field，拒绝外部访问：

class Person {
    private String name;
    private int age;
}
试试private修饰的field有什么效果：

// private field
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.name = "Xiao Ming"; // 对字段name赋值
        ming.age = 12; // 对字段age赋值
    }
}

class Person {
    private String name;
    private int age;
}


是不是编译报错？把访问field的赋值语句去了就可以正常编译了。

把field从public改成private，外部代码不能访问这些field，那我们定义这些field有什么用？怎么才能给它赋值？怎么才能读取它的值？

所以我们需要使用方法（method）来让外部代码可以间接修改field：

// private field
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setName("Xiao Ming"); // 设置name
        ming.setAge(12); // 设置age
        System.out.println(ming.getName() + ", " + ming.getAge());
    }
}

class Person {
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            throw new IllegalArgumentException("invalid age value");
        }
        this.age = age;
    }
}


虽然外部代码不能直接修改private字段，但是，外部代码可以调用方法setName()和setAge()来间接修改private字段。在方法内部，
我们就有机会检查参数对不对。比如，setAge()就会检查传入的参数，参数超出了范围，直接报错。这样，外部代码就没有任何机会把age设置成不合理的值。

对setName()方法同样可以做检查，例如，不允许传入null和空字符串：

public void setName(String name) {
    if (name == null || name.isBlank()) {
        throw new IllegalArgumentException("invalid name");
    }
    this.name = name.strip(); // 去掉首尾空格
}
同样，外部代码不能直接读取private字段，但可以通过getName()和getAge()间接获取private字段的值。

所以，一个类通过定义方法，就可以给外部代码暴露一些操作的接口，同时，内部自己保证逻辑一致性。

调用方法的语法是实例变量.方法名(参数);。一个方法调用就是一个语句，所以不要忘了在末尾加;。例如：ming.setName("Xiao Ming");。

《定义方法》
从上面的代码可以看出，定义方法的语法是：

修饰符 方法返回类型 方法名(方法参数列表) {
    若干方法语句;
    return 方法返回值;
}
方法返回值通过return语句实现，如果没有返回值，返回类型设置为void，可以省略return。

《private方法》
有public方法，自然就有private方法。和private字段一样，private方法不允许外部调用，那我们定义private方法有什么用？

定义private方法的理由是内部方法是可以调用private方法的。例如：

// private method
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setBirth(2008);
        System.out.println(ming.getAge());
    }
}

class Person {
    private String name;
    private int birth;

    public void setBirth(int birth) {
        this.birth = birth;
    }

    public int getAge() {
        return calcAge(2019); // 调用private方法
    }

    // private方法:
    private int calcAge(int currentYear) {
        return currentYear - this.birth;
    }
}

观察上述代码，calcAge()是一个private方法，外部代码无法调用，但是，内部方法getAge()可以调用它。

此外，我们还注意到，这个Person类只定义了birth字段，没有定义age字段，获取age时，通过方法getAge()返回的是一个实时计算的值，
并非存储在某个字段的值。这说明方法可以封装一个类的对外接口，调用方不需要知道也不关心Person实例在内部到底有没有age字段。

《this变量》
在方法内部，可以使用一个隐含的变量this，它始终指向当前实例。因此，通过this.field就可以访问当前实例的字段。

如果没有命名冲突，可以省略this。例如：

class Person {
    private String name;

    public String getName() {
        return name; // 相当于this.name
    }
}
但是，如果有局部变量和字段重名，那么局部变量优先级更高，就必须加上this：

class Person {
    private String name;

    public void setName(String name) {
        this.name = name; // 前面的this不可少，少了就变成局部变量name了
    }
}

《方法参数》
方法可以包含0个或任意个参数。方法参数用于接收传递给方法的变量值。调用方法时，必须严格按照参数的定义一一传递。例如：

class Person {
    ...
    public void setNameAndAge(String name, int age) {
        ...
    }
}
调用这个setNameAndAge()方法时，必须有两个参数，且第一个参数必须为String，第二个参数必须为int：

Person ming = new Person();
ming.setNameAndAge("Xiao Ming"); // 编译错误：参数个数不对
ming.setNameAndAge(12, "Xiao Ming"); // 编译错误：参数类型不对

《可变参数》
可变参数用类型...定义，可变参数相当于数组类型：

class Group {
    private String[] names;

    public void setNames(String... names) {
        this.names = names;
    }
}
上面的setNames()就定义了一个可变参数。调用时，可以这么写：

Group g = new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String
完全可以把可变参数改写为String[]类型：

class Group {
    private String[] names;

    public void setNames(String[] names) {
        this.names = names;
    }
}
但是，调用方需要自己先构造String[]，比较麻烦。例如：

Group g = new Group();
g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"}); // 传入1个String[]
另一个问题是，调用方可以传入null：

Group g = new Group();
g.setNames(null);
而可变参数可以保证无法传入null，因为传入0个参数时，接收到的实际值是一个空数组而不是null。

《参数绑定》
调用方把参数传递给实例方法时，调用时传递的值会按参数位置一一绑定。

那什么是参数绑定？

我们先观察一个基本类型参数的传递：

// 基本类型参数绑定
public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        int n = 15; // n的值为15
        p.setAge(n); // 传入n的值
        System.out.println(p.getAge()); // 15
        n = 20; // n的值改为20
        System.out.println(p.getAge()); // 15还是20?
    }
}

class Person {
    private int age;

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

运行代码，从结果可知，修改外部的局部变量n，不影响实例p的age字段，原因是setAge()方法获得的参数，复制了n的值，因此，p.age和局部变量n互不影响。

结论：基本类型参数的传递，是调用方值的复制。双方各自的后续修改，互不影响。

我们再看一个传递引用参数的例子：

// 引用类型参数绑定
public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        String[] fullname = new String[] { "Homer", "Simpson" };
        p.setName(fullname); // 传入fullname数组
        System.out.println(p.getName()); // "Homer Simpson"
        fullname[0] = "Bart"; // fullname数组的第一个元素修改为"Bart"
        System.out.println(p.getName()); // "Homer Simpson"还是"Bart Simpson"?
    }
}

class Person {
    private String[] name;

    public String getName() {
        return this.name[0] + " " + this.name[1];
    }

    public void setName(String[] name) {
        this.name = name;
    }
}

注意到setName()的参数现在是一个数组。一开始，把fullname数组传进去，然后，修改fullname数组的内容，结果发现，实例p的字段p.name也被修改了！

结论：引用类型参数的传递，调用方的变量，和接收方的参数变量，指向的是同一个对象。双方任意一方对这个对象的修改，都会影响对方（因为指向同一个对象嘛）。

有了上面的结论，我们再看一个例子：

// 引用类型参数绑定
public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        String bob = "Bob";
        p.setName(bob); // 传入bob变量
        System.out.println(p.getName()); // "Bob"
        bob = "Alice"; // bob改名为Alice
        System.out.println(p.getName()); // "Bob"还是"Alice"?
    }
}

class Person {
    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

不要怀疑引用参数绑定的机制，试解释为什么上面的代码两次输出都是"Bob"。

练习
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setName("小明");
        ming.setAge(12);
        System.out.println(ming.getAge());
    }
}
class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 Run
从下载练习：给Person类增加getAge/setAge方法 （推荐使用IDE练习插件快速下载）

小结
方法可以让外部代码安全地访问实例字段；

方法是一组执行语句，并且可以执行任意逻辑；

方法内部遇到return时返回，void表示不返回任何值（注意和返回null不同）；

外部代码通过public方法操作实例，内部代码可以调用private方法；

理解方法的参数绑定。




//public class 方法 {
//	public static void main(String[] args) {
//		printMax(34, 13, 56, 23, 1, 3, 4, 6, 100);
//		printMax(new double[] { 3, 5, 12, 87, 43 });
//	}
//
//	public static void printMax(double... numbers) {
//		if (numbers.length == 0) {
//			System.out.println("No argument passed!");
//			return;
//		}
//		double result = numbers[0];
//		for (int i = 1; i < numbers.length; i++) {
//			if (numbers[i] > result) {
//				result = numbers[i];
//			}
//		}
//		System.out.println("The max value is:" + result);
//	}
//}


//可变参数
//可变参数用类型...定义，可变参数相当于数组类型：

//public class 方法{
//	public static void main(String[] args) {
//		class Group {
//		    private String[] names;
//
//		    public void setNames(String... names) {
//		        this.names = names;
//		    }
//		}
//		Group g = new Group();
//		g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 传入3个String
//		g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
//		g.setNames("Xiao Ming"); // 传入1个String
//		g.setNames(); // 传入0个String
//	}
//}

//完全可以把可变参数改写为String[]类型：
//public class 方法{
//	public static void main(String[] args) {
//		class Group {
//		    private String[] names;
//
//		    public void setNames(String[] names) {
//		        this.names = names;
//		    }
//		}
////		但是，调用方需要自己先构造String[]，比较麻烦。例如：
//
//		Group g = new Group();
//		g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"}); // 传入1个String[]
//	}
//}

//参数传递：
//
//基本类型参数的传递，是调用方值的复制。双方各自的后续修改，互不影响。引用类型参数的传递，调用方的变量，和接收方的参数变量，
//指向的是同一个对象。双方任意一方对这个对象的修改，都会影响对方（因为指向同一个对象嘛）。
//
//我们再看一个传递引用参数的例子：

//引用类型参数绑定
//public class 方法 {
//public static void main(String[] args) {
//   Persona p = new Persona();

//   String[] fullname = new String[] { "Homer", "Simpson" }; //fullname[0] -> "Homer"
//定义数组时，由于是String数组，所以fullname[0] 存储的实际上是字符串 "Homer"的地址；同理，fullname[1] 指向 字符串"Simpson" 的地址


//   p.setName(fullname); // 传入fullname数组  p.name[0] -> fullname[0] -> "Homer"
//实例化对象p的时候，传入了fullname指向的地址，所以name[0]指向fullname[0]在内存中的地址，而fullname[0] 又指向字符串"Homer"


//   System.out.println(p.getName()); // "Homer Simpson"

//   fullname[0] = "Bart"; // fullname数组的第一个元素修改为"Bart" fullname[0] -> "Bart"
//修改fullname[0]时，就是改变 fullname[0] 的指向，修改后为fullname[0]存储的是字符串"Bart"的地址

//   System.out.println(p.getName()); // "Bart Simpson"  p.name[0] -> fullname[0] -> "Bart"
//而此时p.name[0] 的指向并没有变，还是指向fullname[0]所在的地址，访问p.name[0] 时先在内存中找到fullname[0]，又根据fullname[0]中存储的地址信息访问到"Bart"


//}
//}
//
//class Persona {
//private String[] name;
//
//public String getName() {
//   return this.name[0] + " " + this.name[1];
//}
//
//public void setName(String[] name) {
//   this.name = name;
//}
//}

//public class 方法 {
//  public static void main(String[] args) {
//      Persona p = new Persona();
//      String bob = "Bob";// 定义字符串时，bob存储了字符串"Bob"在内存中的地址:bob -> "Bob"
//      p.setName(bob); // 传入bob变量 实例化对象p的时候，给p.name传入了bob 存储的地址，即"Bob"在内存中的地址:p.name -> "Bob"
//      System.out.println(p.getName()); // "Bob"
//      bob = "Alice"; // bob改名为Alice   给bob赋值后，bob指向了内存中新的字符串"Alice"的地址:bob -> "Alice"
//      System.out.println(bob);//"Alice"
//      System.out.println(p.getName()); // "Bob" 此时p.name仍然指向 "Bob"在内存中的地址:p.name -> "Bob"
//      
//      String[] names = {"ABC", "XYZ", "zoo"};
//      String s = names[1];
//      names[1] = "cat";
//      System.out.println(s); // "XYZ"
//  }
//}
//
//class Persona {
//  private String name;
//
//  public String getName() {
//      return this.name;
//  }
//
//  public void setName(String name) {
//      this.name = name;
//  }
//}


//字符串和数组属性的类实例化后，变量值发生变化会产生不同结果：
//
//字符串：
//
//1. 定义字符串时，bob存储了字符串"Bob"在内存中的地址
//String bob = "Bob" // bob -> "Bob"
//
//2. 实例化对象p的时候，给p.name传入了bob 存储的地址，即"Bob"在内存中的地址
//p.setName(bob)  // p.name -> "Bob"
//
//3.  给bob赋值后，bob指向了内存中新的字符串"Alice"的地址
//bob = "Alice" // bob -> "Alice" 
//
//4. 此时p.name仍然指向 "Bob"在内存中的地址
////p.name -> "Bob"
//
//数组：
//
//1. 定义数组时，由于是String数组，所以fullname[0] 存储的实际上是字符串 "Homer"的地址；同理，fullname[1] 指向 字符串"Simpson" 的地址
//
//String[] fullname = new String[] { "Homer", "Simpson" }; // fullname[0] -> "Homer"  
//
//2. 实例化对象p的时候，传入了fullname指向的地址，所以name[0]指向fullname[0]在内存中的地址，而fullname[0] 又指向字符串"Homer"
//p.setName(fullname); //p.name[0] -> fullname[0] -> "Homer"
//
//3. 修改fullname[0]时，就是改变 fullname[0] 的指向，修改后为fullname[0]存储的是字符串"Bart"的地址
//fullname[0] = "Bart" // fullname[0] -> "Bart"
//
//4.  而此时p.name[0] 的指向并没有变，还是指向fullname[0]所在的地址，访问p.name[0] 时先在内存中找到fullname[0]，又根据fullname[0]中存储的地址信息访问到"Bart"
//
//fullname[0] = "Bart" // p.name[0] -> fullname[0] -> "Bart"